/**
 * Test-case generator for flow summaries. See the accompanying `GenerateFlowTestCase.py` for full
 * documentation and usage information.
 */

import java
private import semmle.code.java.dataflow.internal.DataFlowUtil
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.FlowSummary
private import semmle.code.java.dataflow.internal.FlowSummaryImpl
import FlowTestCase
private import FlowTestCaseSupportMethods
private import FlowTestCaseUtils

/**
 * Gets a CSV row for which a test has been requested, and where there exists a summary, but
 * nonetheless we can't generate a test case for it, indicating we cannot resolve either the callable
 * spec or an input or output spec.
 */
query string getAParseFailure(string reason) {
  any(TargetSummaryModelCsv target).row(result) and
  summaryModelRow(_, _, _, _, _, _, _, _, _, _, result) and
  (
    not summaryModelRow(_, _, _, _, _, _, _, _, _, _, result) and
    reason = "row could not be parsed"
    or
    exists(
      string namespace, string type, boolean subtypes, string name, string signature, string ext
    |
      summaryModelRow(namespace, type, subtypes, name, signature, ext, _, _, _, _, result) and
      not interpretElement(namespace, type, subtypes, name, signature, ext) instanceof Callable and
      reason = "callable could not be resolved"
    )
    or
    exists(string inputSpec |
      summaryModelRow(_, _, _, _, _, _, inputSpec, _, _, _, result) and
      not Private::External::interpretSpec(inputSpec, _) and
      reason = "input spec could not be parsed"
    )
    or
    exists(string outputSpec |
      summaryModelRow(_, _, _, _, _, _, _, outputSpec, _, _, result) and
      not Private::External::interpretSpec(outputSpec, _) and
      reason = "output spec could not be parsed"
    )
  )
}

/**
 * Gets a CSV row for which a test was requested and was correctly parsed,
 * but for which no test case could be generated due to a limitation of the query.
 */
query string noTestCaseGenerated() {
  any(TargetSummaryModelCsv target).row(result) and
  summaryModelRow(_, _, _, _, _, _, _, _, _, _, result) and
  not exists(getAParseFailure(_)) and
  not exists(any(TestCase tc).getATestSnippetForRow(result))
}

/**
 * Gets a valid test case, i.e. one that has a test snippet.
 */
TestCase getAValidTestCase() { exists(result.getATestSnippetForRow(_)) }

/**
 * Returns an import statement to include in the test case header.
 */
string getAnImportStatement() {
  exists(RefType t |
    t = getAValidTestCase().getADesiredImport() and
    isImportable(t) and
    t.getPackage().getName() != "java.lang"
  |
    result = "import " + t.getPackage().getName() + "." + t.getName() + ";"
  )
}

/**
 * Returns a support method to include in the generated test class.
 */
SupportMethod getASupportMethod() {
  result instanceof SourceMethod or
  result instanceof SinkMethod or
  result = getAValidTestCase().getASupportMethod()
}

/**
 * Returns a data extension specification of the taint-/value-propagation behavior of a test support method (`get` or `newWith` method).
 */
query string getASupportMethodModel() { result = getASupportMethod().getDataExtensionModel() }

/**
 * Gets a Java file body testing all requested Models as Data rows against whatever classes and methods they resolve against.
 */
query string getTestCase() {
  result =
    "package generatedtest;\n\n" + concat(getAnImportStatement() + "\n") +
      "\n// Test case generated by GenerateFlowTestCase.ql\npublic class Test {\n\n" +
      concat("\t" + getASupportMethod().getDefinition() + "\n") +
      "\n\tpublic void test() throws Exception {\n\n" +
      concat(string row, string snippet |
        snippet = any(TestCase tc).getATestSnippetForRow(row)
      |
        snippet order by row
      ) + "\n\t}\n\n}"
}
